﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using NMapper.Utils;

namespace NMapper.Mappings
{
    public class NormalizeMapping<TDatabase, TDatabaseEntity, TOutput, TOutputValue> : IOutputValueMapping<TDatabase, TOutput, TOutputValue>
        where TOutputValue : class
        where TDatabase : IDisposable
    {
        private Expression<Func<TOutput, TOutputValue>> outputSelector;
        private Func<TOutput, TOutputValue> outputSelectorFunc;
        private Mapper<TDatabase, TDatabaseEntity, TOutputValue> mapper;
        private PropertyInfo property;

        public NormalizeMapping(Expression<Func<TOutput, TOutputValue>> outputSelector, Mapper<TDatabase, TDatabaseEntity, TOutputValue> mapper)
        {
            this.outputSelector = outputSelector;
            this.mapper = mapper;
            outputSelectorFunc = outputSelector.Compile();
        }

        Func<object, object> IOutputSelector.OutputSelector
        {
            get { return x => OutputSelector((TOutput)x); }
        }

        public Func<TOutput, TOutputValue> OutputSelector
        {
            get { return outputSelectorFunc; }
        }

        public PropertyInfo TransientProperty
        {
            get { return property; }
        }

        LambdaExpression IOutputSelector.OutputSelectorExpression
        {
            get { return outputSelector; }
        }

        public void DefineTransientType(ModuleBuilder module, TypeBuilder type)
        {
            mapper.Initialize();
            var propertyName = NameHelpers.SuggestName(outputSelector);
            property = type.DefineProperty(propertyName, mapper.TransientType);
        }

        public void DefineTransientInitialization(MapContext<TDatabase> mapContext, Type transientType, List<MemberBinding> initializers, ParameterExpression parameterExpression)
        {
            property = transientType.GetProperty(property.Name);

            var lambdaBinder = new LambdaBinder();
            var subSelector = mapper.CreateTransientSelector(mapContext);

            var boundSubSelector = lambdaBinder.BindBody(subSelector, parameterExpression);

            var initializer = Expression.Bind(property.GetSetMethod(), boundSubSelector);
            initializers.Add(initializer);
        }

        void IMapping<TDatabase>.OnTransientInitialized(MapContext<TDatabase> mapContext, Array transient)
        {
        }

        public void MapToOutput(MapContext<TDatabase> mapContext, object input, object output)
        {
            var outputProperty = outputSelector.GetPropertyInfo();
            var value = property.GetValue(input, null);

            if (value != null)
                value = mapper.TransformToOutput(mapContext, new[] { value })[0];

            outputProperty.SetValue(output, value, null);
        }

        public void MapToInput(MapContext<TDatabase> mapContext, object output, object input)
        {
            var outputValue = OutputSelector((TOutput)output);

            if (outputValue != null)
                mapper.MapToInput(mapContext, outputValue, input);
        }

        public override string ToString()
        {
            return string.Format("NormalizeMapping({0}, {1})", outputSelector, mapper);
        }
    }
}